package com.agile4j.oss.adapter.impl;

import cn.hutool.core.lang.Assert;
import com.agile4j.oss.adapter.DeleteError;
import com.agile4j.oss.adapter.OssClientAdapter;
import com.agile4j.oss.adapter.Result;
import com.agile4j.oss.core.OssProperties;
import com.cs.gzhn.ulid.UlidBuilder;
import io.minio.errors.*;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.ResponseBytes;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;

import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Component
@ConditionalOnProperty(name = "oss.type", havingValue = "s3")
public class S3ClientAdapter implements OssClientAdapter {

    @Autowired
    private S3Client s3Client;

    @Autowired
    // 推荐注入
    private S3Presigner s3Presigner;




    @Autowired
    private OssProperties ossProperties;

    private static final Logger log = LoggerFactory.getLogger(S3ClientAdapter.class);
    final String PATH_SEPARATOR = "/";

    /**
     * description: 判断bucket是否存在，不存在则创建
     */
    @Override
    public void existBucket(String name) {
        try {
            HeadBucketRequest headBucketRequest = HeadBucketRequest.builder().bucket(name).build();
            try {
                s3Client.headBucket(headBucketRequest);
            } catch (S3Exception e) {
                if (e.statusCode() == 404) {
                    // Bucket不存在，创建它
                    CreateBucketRequest createBucketRequest = CreateBucketRequest.builder()
                            .bucket(name)
                            .build();
                    s3Client.createBucket(createBucketRequest);
                    
                    // 等待bucket创建完成（S3有时需要一点时间）
                    s3Client.waiter().waitUntilBucketExists(
                        HeadBucketRequest.builder().bucket(name).build()
                    );
                } else {
                    throw e;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建存储bucket
     *
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    @Override
    public Boolean makeBucket(String bucketName) {
        try {
            CreateBucketRequest createBucketRequest = CreateBucketRequest.builder()
                    .bucket(bucketName)
                    .build();
            s3Client.createBucket(createBucketRequest);
            
            // 等待bucket创建完成
            s3Client.waiter().waitUntilBucketExists(
                HeadBucketRequest.builder().bucket(bucketName).build()
            );
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除存储bucket
     *
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    @Override
    public Boolean removeBucket(String bucketName) {
        try {
            // 首先删除bucket中的所有对象
            listObjects(bucketName).forEach(obj -> {
                String key = (String) obj.get("fileName");
                deleteObject(bucketName, key);
            });
            
            // 删除bucket
            DeleteBucketRequest deleteBucketRequest = DeleteBucketRequest.builder()
                    .bucket(bucketName)
                    .build();
            s3Client.deleteBucket(deleteBucketRequest);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 辅助方法：删除单个对象
     */
    private void deleteObject(String bucketName, String key) {
        try {
            DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
                    .bucket(bucketName)
                    .key(key)
                    .build();
            s3Client.deleteObject(deleteObjectRequest);
        } catch (Exception e) {
            log.error("删除对象失败: bucket={}, key={}", bucketName, key, e);
        }
    }

    /**
     * description: 下载文件
     */
    @Override
    public String download(String url, String path) {
        String pre = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName();
        String fileName = url.startsWith(pre) ? url.replace(pre, "") : url;
        log.info(" download pre is {} ", pre);
        log.info(" download fileName is {} ", fileName);
        InputStream in = null;
        FileOutputStream fos = null;
        try {
            GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                    .bucket(ossProperties.getBucketName())
                    .key(fileName)
                    .build();
            
            ResponseInputStream<GetObjectResponse> response = s3Client.getObject(getObjectRequest);
            in = response;
            
            String realFileName = fileName.contains("/") ? fileName.substring(fileName.lastIndexOf("/") + 1) : fileName;
            File file = new File(path);
            if (path.contains(realFileName)) {
                log.info("file  mkdirs");
                File pfile = file.getParentFile();
                if (!pfile.exists()) {
                    pfile.mkdirs();
                }
            } else {
                log.info("file isDirectory  mkdirs");
                file.mkdirs();
            }
            log.info(" write file is {} ", path + PATH_SEPARATOR + realFileName);
            fos = new FileOutputStream(path + PATH_SEPARATOR + realFileName);
            byte[] b = new byte[1024];
            int length;
            while ((length = in.read(b)) > 0) {
                fos.write(b, 0, length);
            }
            return path + PATH_SEPARATOR + realFileName;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * description: 下载文件
     */
    @Override
    public InputStream download(String url) {
        try {
            String pre = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName();
            String fileName = url.startsWith(pre) ? url.replace(pre, "") : url;
            
            GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                    .bucket(ossProperties.getBucketName())
                    .key(fileName)
                    .build();
            
            return s3Client.getObject(getObjectRequest);
        } catch (Exception e) {
            log.error("S3下载失败", e);
        }
        return null;
    }

    /**
     * 下载文件为字节数组
     */
    @Override
    public byte[] downloadByte(String url) {
        try {
            if (StringUtils.containsIgnoreCase(url, "Credential")) {
                log.info("downloadByte url is getPreviewUrl {}", url);
                url = StringUtils.substringBeforeLast(url, "?");
            }
            if (!StringUtils.startsWithIgnoreCase(url, "/")) {
                url = "/" + url;
            }
            
            Assert.isTrue(checkFileIsExist(ossProperties.getBucketName(), url), "下载对象不存在");
            
            String pre = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName();
            String fileName = url.startsWith(pre) ? url.replace(pre, "") : url;
            
            GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                    .bucket(ossProperties.getBucketName())
                    .key(fileName)
                    .build();
            
            ResponseBytes<GetObjectResponse> objectBytes = s3Client.getObjectAsBytes(getObjectRequest);
            return objectBytes.asByteArray();
            
        } catch (Exception e) {
            log.error("S3下载失败", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * description: 下载文件
     */
    @Override
    public ResponseEntity<byte[]> downloadByUrl(String url) {
        String pre = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName();
        String fileName = url.startsWith(pre) ? url.replace(pre, "") : url;
        ResponseEntity<byte[]> responseEntity = null;
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                    .bucket(ossProperties.getBucketName())
                    .key(fileName)
                    .build();
            
            ResponseInputStream<GetObjectResponse> response = s3Client.getObject(getObjectRequest);
            in = response;
            out = new ByteArrayOutputStream();
            Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
            IOUtils.copy(in, writer,StandardCharsets.UTF_8);
            
            // 封装返回值
            byte[] bytes = out.toByteArray();
            HttpHeaders headers = new HttpHeaders();
            try {
                String realFileName = fileName.contains("/") ? fileName.substring(fileName.lastIndexOf("/") + 1) : fileName;
                headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(realFileName, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            headers.setContentLength(bytes.length);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setAccessControlExposeHeaders(Arrays.asList("*"));
            responseEntity = new ResponseEntity<>(bytes, headers, HttpStatus.OK);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseEntity;
    }

    /**
     * 批量删除文件对象
     */
    @Override
    public Iterable<Result<DeleteError>> removeObjects(String bucketName, List<String> objects) {
        try {
            List<ObjectIdentifier> objectIds = objects.stream()
                    .map(key -> ObjectIdentifier.builder().key(key).build())
                    .collect(Collectors.toList());
            
            DeleteObjectsRequest deleteObjectsRequest = DeleteObjectsRequest.builder()
                    .bucket(bucketName)
                    .delete(software.amazon.awssdk.services.s3.model.Delete.builder()
                            .objects(objectIds)
                            .build())
                    .build();
            
            DeleteObjectsResponse response = s3Client.deleteObjects(deleteObjectsRequest);
            
            // 转换为与MinIO兼容的返回格式
            List<Result<DeleteError>> results = new ArrayList<>();
            if (response.hasErrors()) {
                for (software.amazon.awssdk.services.s3.model.S3Error error : response.errors()) {
                    DeleteError deleteError = new DeleteError();
                    // 这里需要创建一个适配器类或使用反射，为了简化，我们创建一个简单的包装
                    // 实际项目中可能需要创建一个适配器类
                    Result<DeleteError> result = new Result<DeleteError>() {
                        @Override
                        public DeleteError get() throws IOException, InvalidKeyException, NoSuchAlgorithmException, XmlParserException, ServerException, InsufficientDataException, InternalException, InvalidResponseException, ErrorResponseException {
                            return deleteError;
                        }
                        
                        @Override
                        public Exception error() {
                            return null;
                        }
                    };
                    results.add(result);
                }
            }
            return results;
        } catch (Exception e) {
            log.error("批量删除失败", e);
            return Collections.emptyList();
        }
    }

    /**
     * 删除文件对象(单个)
     */
    @Override
    public String delete(String module, String fileName) {
        String name = PATH_SEPARATOR + module + PATH_SEPARATOR + fileName;
        try {
            if (!checkFileIsExist(ossProperties.getBucketName(), name)) {
                return "该文件不存在";
            }
            DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
                    .bucket(ossProperties.getBucketName())
                    .key(name)
                    .build();
            s3Client.deleteObject(deleteObjectRequest);
        } catch (Exception e) {
            log.error("删除失败", e);
        }
        log.info("删除成功");
        return ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName() + name;
    }

    /**
     * 判断文件是否存在
     */
    @Override
    public Boolean checkFileIsExist(String bucketName, String objectName) {
        try {
            HeadObjectRequest headObjectRequest = HeadObjectRequest.builder()
                    .bucket(bucketName)
                    .key(objectName)
                    .build();
            s3Client.headObject(headObjectRequest);
            return true;
        } catch (S3Exception e) {
            if (e.statusCode() == 404) {
                return false;
            }
            throw e;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 复制对象
     */
    @Override
    public Map<String, Object> copyObject(String sourcePath, String copyPath) throws RuntimeException {
        String fileName = org.apache.commons.lang3.StringUtils.substringAfterLast(sourcePath, PATH_SEPARATOR);
        
        try {
            // 检查源文件是否存在
            HeadObjectRequest headRequest = HeadObjectRequest.builder()
                    .bucket(ossProperties.getBucketName())
                    .key(sourcePath)
                    .build();
            HeadObjectResponse headResponse = s3Client.headObject(headRequest);
            
            // 执行复制
            CopyObjectRequest copyRequest = CopyObjectRequest.builder()
                    .copySource(ossProperties.getBucketName() + "/" + sourcePath)
                    .destinationBucket(ossProperties.getBucketName())
                    .destinationKey(copyPath)
                    .build();

            s3Client.copyObject(copyRequest);
            
            Map<String, Object> sysFile = new HashMap<>(16);
            sysFile.put("multipartFileName", fileName);
            sysFile.put("fileType", org.apache.commons.lang3.StringUtils.substringAfterLast(fileName, "."));
            sysFile.put("referFlag", false);
            sysFile.put("fileName", fileName);
            String url = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName() + copyPath;
            sysFile.put("url", url);
            sysFile.put("fileUrl", url);
            
            // 获取文件大小和MD5
            sysFile.put("fileSize", headResponse.contentLength());
            sysFile.put("size", headResponse.contentLength());
            sysFile.put("mD5", headResponse.eTag().replace("\"", "")); // S3的ETag通常用作MD5
            
            return sysFile;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 上传文件
     */
    @Override
    public Map<String, Object> upload(MultipartFile multipartFile, String path) {
        if (!path.endsWith(PATH_SEPARATOR)) {
            path += PATH_SEPARATOR;
        }
        if (!path.startsWith(PATH_SEPARATOR)) {
            path = PATH_SEPARATOR + path;
        }
        String name = multipartFile.getOriginalFilename();
        String fileType = getExtension(multipartFile);
        File file = null;
        try {
            file = File.createTempFile("upload_", "_" + name);
            multipartFile.transferTo(Paths.get(file.getPath()));
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
        
        log.info("createTempFile file {}", file.getPath());
        
        String fileName = path + UlidBuilder.getUlid(System.currentTimeMillis()) + '.' + fileType;
        log.info("multipartFile getOriginalFilename {}, fileType {} fileName {}", name, fileType, fileName);
        
        InputStream in = null;
        try {
            in = new FileInputStream(file);
            
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                    .bucket(ossProperties.getBucketName())
                    .key(fileName)
                    .contentType(multipartFile.getContentType())
                    .contentLength(multipartFile.getSize())
                    .build();
                    
            s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(in, multipartFile.getSize()));
            
            Map<String, Object> sysFile = new HashMap<>(16);
            sysFile.put("multipartFileName", name);
            sysFile.put("fileType", fileType);
            sysFile.put("referFlag", false);
            sysFile.put("fileName", fileName);
            
            String url = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName() + fileName;
            if (!StringUtils.startsWithIgnoreCase(fileName, PATH_SEPARATOR)) {
                url = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName() + PATH_SEPARATOR + fileName;
            } else {
                url = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName() + fileName;
            }
            
            log.info("oss upload file {}", url);
            sysFile.put("fileUrl", url);
            sysFile.put("url", url);
            sysFile.put("createTime", LocalDateTime.now());
            sysFile.put("fileSize", multipartFile.getSize());
            sysFile.put("size", multipartFile.getSize());
            
            // 重新读取文件计算MD5（因为InputStream已经被消费）
            try (FileInputStream fis = new FileInputStream(file)) {
                sysFile.put("MD5", DigestUtils.md5DigestAsHex(fis));
            }
            
            if (isImage(fileType)) {
                sysFile.put("previewUrl", url);
            } else {
                sysFile.put("previewUrl", getPreviewUrl(url));
            }
            
            return sysFile;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
            // 清理临时文件
            if (file != null && file.exists()) {
                file.delete();
            }
        }
    }

    /**
     * 上传文件（使用FileInputStream）
     */
    @Override
    public Map<String, Object> upload(FileInputStream inputStream, String path, String fileName) {
        if (!path.endsWith(PATH_SEPARATOR)) {
            path += PATH_SEPARATOR;
        }
        if (!path.startsWith(PATH_SEPARATOR)) {
            path = PATH_SEPARATOR + path;
        }
        
        String fileType = getExtension(fileName);
        String name = StringUtils.substringBeforeLast(fileName, ".");
        String newFileName = path + UlidBuilder.getUlid(System.currentTimeMillis()) + '.' + fileType;
        
        log.info("multipartFile getOriginalFilename {}, fileType {} fileName {}", name, fileType, newFileName);
        
        try {
            FileChannel channel = inputStream.getChannel();
            long size = channel.size();
            
            // 由于S3需要知道内容长度，我们需要先读取到内存或临时文件
            byte[] fileBytes = inputStream.readAllBytes();
            
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                    .bucket(ossProperties.getBucketName())
                    .key(newFileName)
                    .contentType(getContentType(fileType))
                    .contentLength((long) fileBytes.length)
                    .build();
                    
            s3Client.putObject(putObjectRequest, RequestBody.fromBytes(fileBytes));
            
            Map<String, Object> sysFile = new HashMap<>(16);
            sysFile.put("multipartFileName", name);
            sysFile.put("fileType", fileType);
            sysFile.put("referFlag", false);
            sysFile.put("fileName", newFileName);
            
            String url = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName() + newFileName;
            if (!StringUtils.startsWithIgnoreCase(newFileName, PATH_SEPARATOR)) {
                url = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName() + PATH_SEPARATOR + newFileName;
            } else {
                url = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName() + newFileName;
            }
            
            log.info("oss upload file {}", url);
            sysFile.put("fileUrl", url);
            sysFile.put("url", url);
            sysFile.put("createTime", LocalDateTime.now());
            sysFile.put("fileSize", size);
            sysFile.put("size", size);
            sysFile.put("MD5", DigestUtils.md5DigestAsHex(fileBytes));
            
            if (isImage(fileType)) {
                sysFile.put("previewUrl", url);
            } else {
                sysFile.put("previewUrl", getPreviewUrl(url));
            }
                
            return sysFile;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        } finally {
            log.info("oss upload file {}", newFileName);
        }
    }

    /**
     * 根据文件类型获取MIME类型
     */
    private String getContentType(String fileType) {
        switch (fileType.toLowerCase()) {
            case "png":
                return "image/png";
            case "jpg":
            case "jpeg":
                return "image/jpeg";
            case "bmp":
                return "image/bmp";
            case "gif":
                return "image/gif";
            case "pdf":
                return "application/pdf";
            case "txt":
                return "text/plain";
            case "doc":
                return "application/msword";
            case "docx":
                return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
            case "xls":
                return "application/vnd.ms-excel";
            case "xlsx":
                return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
            default:
                return "application/octet-stream";
        }
    }

    public static final String getExtension(@NotNull MultipartFile file) {
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        if (StringUtils.isEmpty(extension)) {
            extension = getExtension(Objects.requireNonNull(file.getContentType()));
        }
        return extension;
    }

    private static final Pattern IMAGE_PATTERN =
            Pattern.compile("\\.(jpg|jpeg|png|gif|webp|bmp|tiff|tif|svg|ico)$", Pattern.CASE_INSENSITIVE);

    public static boolean isImage(String filename) {
        return IMAGE_PATTERN.matcher(filename).find();
    }

    public static final String IMAGE_PNG = "image/png";
    public static final String IMAGE_JPG = "image/jpg";
    public static final String IMAGE_JPEG = "image/jpeg";
    public static final String IMAGE_BMP = "image/bmp";
    public static final String IMAGE_GIF = "image/gif";

    public static String getExtension(String prefix) {
        switch (prefix) {
            case IMAGE_PNG:
                return "png";
            case IMAGE_JPG:
            case IMAGE_JPEG:
                return "jpg";
            case IMAGE_BMP:
                return "bmp";
            case IMAGE_GIF:
                return "gif";
            default:
                return "";
        }
    }

    /**
     * 上传文件（保持原始文件名）
     */
    @Override
    public Map<String, Object> uploadRel(MultipartFile multipartFile, String path) {
        if (!path.endsWith(PATH_SEPARATOR)) {
            path += PATH_SEPARATOR;
        }
        if (!path.startsWith(PATH_SEPARATOR)) {
            path = PATH_SEPARATOR + path;
        }
            
        String name = multipartFile.getOriginalFilename();
        String fileType = StringUtils.substringAfterLast(name, ".");
        String fileName = path + name;
        if (!fileName.contains(fileType)) {
            fileName = fileName + fileType;
        }
        
        log.info("putObject fileName{}, {}", fileName, multipartFile.getContentType());
        
        try {
            byte[] fileBytes = multipartFile.getBytes();
            
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                    .bucket(ossProperties.getBucketName())
                    .key(fileName)
                    .contentType(multipartFile.getContentType())
                    .contentLength((long) fileBytes.length)
                    .build();
                    
            s3Client.putObject(putObjectRequest, RequestBody.fromBytes(fileBytes));
            
            Map<String, Object> sysFile = new HashMap<>(16);
            sysFile.put("multipartFileName", name);
            sysFile.put("fileType", fileType);
            sysFile.put("referFlag", false);
            sysFile.put("fileName", fileName);
            
            String url = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName() + fileName;
            if (!StringUtils.startsWithIgnoreCase(fileName, PATH_SEPARATOR)) {
                url = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName() + PATH_SEPARATOR + fileName;
            } else {
                url = ossProperties.getEndpoint() + PATH_SEPARATOR + ossProperties.getBucketName() + fileName;
            }
            
            sysFile.put("fileUrl", url);
            sysFile.put("url", url);
            sysFile.put("createTime", LocalDateTime.now());
            sysFile.put("fileSize", multipartFile.getSize());
            sysFile.put("size", multipartFile.getSize());
            sysFile.put("MD5", DigestUtils.md5DigestAsHex(fileBytes));
            
            if (isImage(fileType)) {
                sysFile.put("previewUrl", url);
            } else {
                sysFile.put("previewUrl", getPreviewUrl(url));
            }
                
            return sysFile;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 查看文件对象
     */
    @Override
    public List<Map<String, Object>> listObjects(String bucketName) {
        List<Map<String, Object>> objectItems = new ArrayList<>();
        try {
            ListObjectsV2Request listObjectsRequest = ListObjectsV2Request.builder()
                    .bucket(bucketName)
                    .build();
                    
            ListObjectsV2Response response = s3Client.listObjectsV2(listObjectsRequest);
            
            for (S3Object s3Object : response.contents()) {
                Map<String, Object> objectItem = new HashMap<>(16);
                objectItem.put("fileName", s3Object.key());
                objectItem.put("size", s3Object.size());
                objectItems.add(objectItem);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
        return objectItems;
    }

    /**
     * 将File转换为MultipartFile
     */
    public MultipartFile toMultipartFile(@NotNull File file) {
        try {
            Path path = Paths.get(file.getAbsolutePath());
            String name = file.getName();
            String originalFileName = file.getName();
            String contentType = Files.probeContentType(path);
            byte[] content = Files.readAllBytes(path);
            
            return new MockMultipartFile(name, originalFileName, contentType, content);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 获取预览URL（带签名的临时URL）
     */
    @Override
    public String getPreviewUrl(String pathUrl) {
            String fileName = StringUtils.substringAfterLast(pathUrl, "/");
            String downUrl = StringUtils.substringAfterLast(pathUrl, ossProperties.getBucketName());
            String url = null;

            log.info("fileName is {}, downUrl is {}", fileName, downUrl);

            try {
                if (StringUtils.startsWithIgnoreCase(downUrl, "/")) {
                    downUrl = StringUtils.substring(downUrl, 1);
                }

                // 1. 构造 GetObjectRequest
                GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                        .bucket(ossProperties.getBucketName())
                        .key(downUrl)
                        .build();

                // 2. 构造预签名请求，设置1小时有效期
                GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
                        .signatureDuration(Duration.ofHours(1)) // ✅ 有效期1小时
                        .getObjectRequest(getObjectRequest)
                        .build();

                // 3. 生成预签名 URL
                URL presignedUrl = s3Presigner.presignGetObject(presignRequest).url();
                url = presignedUrl.toExternalForm();

            } catch (Exception e) {
                log.error("getPresignedObjectUrl error, url:{} {}", downUrl, fileName, e);
                throw new RuntimeException(e);
            }

            log.info("fileName is {}, downUrl is {} file path {} previewUrl {}", fileName, downUrl, pathUrl, url);
            return url;
        }

    @Override
    public List<String> getBucketObjects(String bucketName) {
        return List.of();
    }

    @Override
    public void removeObject(String module, String fileName) {

    }

    @Override
    public void createBucket(String bucketName) {

    }

    @Override
    public void putObject(String module, String path, String filePath) {

    }
}